Skip to content

feat(i18n): localize remaining hardcoded UI surfaces#9082

Open
xuhongbo wants to merge 13 commits into
makeplane:previewfrom
xuhongbo:i18n/localize-hardcoded-ui-v2
Open

feat(i18n): localize remaining hardcoded UI surfaces#9082
xuhongbo wants to merge 13 commits into
makeplane:previewfrom
xuhongbo:i18n/localize-hardcoded-ui-v2

Conversation

@xuhongbo
Copy link
Copy Markdown

@xuhongbo xuhongbo commented May 15, 2026

Description

Closes #9089.

Expands the existing localization coverage by routing UI surfaces that still used hardcoded English through the current i18n namespace JSON system.

Supersedes #9069 and rebases the hardcoded-UI localization work on top of the current preview branch after #8898. This version follows the new react-i18next namespace JSON structure from #8898 instead of adding a catch-all namespace. New strings are added to the relevant existing namespace files: auth, common, editor, empty-state, inbox, page, project, work-item, and workspace.

Scope:

  • Localizes remaining hardcoded UI strings across Web onboarding, inbox, workspace invitations, workspace views, page access, and project settings empty states.
  • Localizes Space public/auth/work-item surfaces, including public board errors, filters, issue peek details, reactions, comments, OAuth, login, and instance failure states.
  • Localizes editor link editing labels and descriptions.
  • Adds matching keys across all supported locales.

Baseline measurement:

  • Scope: changed non-JSON TypeScript/TSX UI files in this PR, compared against the preview base commit.
  • Method: heuristic AST scan for user-facing English literals in JSX text, visible JSX attributes, and label/title/message-like object properties. Locale JSON files are excluded from this count.
  • Result: candidate hardcoded English occurrences dropped from 269 to 13, a 95.2% reduction.
  • Remaining hits are intentionally not localized in this PR because they are brand/contact/example/technical/static metadata values such as support@plane.so, name@company.com, OAuth provider logo alt text, Plane Publish, Plane logo, Popper's preventOverflow, and the static 404 - Page Not Found meta title.

Translation quality notes:

  • Uses the existing namespace JSON structure.
  • Adds matching keys across all supported locale files.
  • Keeps placeholders and interpolation syntax aligned with the English source.
  • Uses the locale sync check as the structural guard for key coverage.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Not included. This is a text/i18n wiring change; affected screens keep the same layout and behavior while rendering localized copy.

Test Scenarios

  • pnpm --filter=@plane/i18n run check:sync
  • pnpm fix:format
  • pnpm turbo run check:lint --filter=@plane/i18n --filter=web --filter=space --filter=@plane/editor --filter=@plane/propel
  • pnpm turbo run check:types --filter=@plane/i18n --filter=web --filter=space --filter=@plane/editor --filter=@plane/propel

References

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Important

Review skipped

Too many files!

This PR contains 300 files, which is 150 over the limit of 150.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afcecc17-b632-4443-ac4f-82e53182f159

📥 Commits

Reviewing files that changed from the base of the PR and between 50a7b47 and 0ccb7e5.

📒 Files selected for processing (300)
  • apps/space/app/error.tsx
  • apps/space/app/not-found.tsx
  • apps/space/components/account/auth-forms/auth-header.tsx
  • apps/space/components/account/auth-forms/auth-root.tsx
  • apps/space/components/account/auth-forms/email.tsx
  • apps/space/components/account/auth-forms/password.tsx
  • apps/space/components/account/auth-forms/unique-code.tsx
  • apps/space/components/account/terms-and-conditions.tsx
  • apps/space/components/account/user-logged-in.tsx
  • apps/space/components/common/powered-by.tsx
  • apps/space/components/instance/instance-failure-view.tsx
  • apps/space/components/issues/filters/labels.tsx
  • apps/space/components/issues/filters/root.tsx
  • apps/space/components/issues/filters/selection.tsx
  • apps/space/components/issues/filters/state.tsx
  • apps/space/components/issues/issue-layouts/error.tsx
  • apps/space/components/issues/issue-layouts/issue-layout-HOC.tsx
  • apps/space/components/issues/issue-layouts/kanban/default.tsx
  • apps/space/components/issues/issue-layouts/kanban/swimlanes.tsx
  • apps/space/components/issues/issue-layouts/list/default.tsx
  • apps/space/components/issues/issue-layouts/properties/all-properties.tsx
  • apps/space/components/issues/issue-layouts/properties/cycle.tsx
  • apps/space/components/issues/issue-layouts/properties/due-date.tsx
  • apps/space/components/issues/issue-layouts/properties/labels.tsx
  • apps/space/components/issues/issue-layouts/properties/member.tsx
  • apps/space/components/issues/issue-layouts/properties/modules.tsx
  • apps/space/components/issues/issue-layouts/properties/priority.tsx
  • apps/space/components/issues/issue-layouts/properties/state.tsx
  • apps/space/components/issues/issue-layouts/utils.tsx
  • apps/space/components/issues/navbar/user-avatar.tsx
  • apps/space/components/issues/peek-overview/comment/add-comment.tsx
  • apps/space/components/issues/peek-overview/comment/comment-detail-card.tsx
  • apps/space/components/issues/peek-overview/header.tsx
  • apps/space/components/issues/peek-overview/issue-activity.tsx
  • apps/space/components/issues/peek-overview/issue-properties.tsx
  • apps/space/components/issues/reactions/issue-vote-reactions.tsx
  • apps/space/components/ui/not-found.tsx
  • apps/space/hooks/oauth/core.tsx
  • apps/web/app/(all)/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx
  • apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx
  • apps/web/app/(all)/[workspaceSlug]/(projects)/sidebar.tsx
  • apps/web/app/(all)/[workspaceSlug]/(projects)/stickies/page.tsx
  • apps/web/app/(all)/[workspaceSlug]/(projects)/workspace-views/page.tsx
  • apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/page.tsx
  • apps/web/app/(all)/workspace-invitations/page.tsx
  • apps/web/app/not-found.tsx
  • apps/web/ce/components/projects/settings/intake/header.tsx
  • apps/web/core/components/analytics/work-items/workitems-insight-table.tsx
  • apps/web/core/components/inbox/content/inbox-issue-header.tsx
  • apps/web/core/components/inbox/content/inbox-issue-mobile-header.tsx
  • apps/web/core/components/inbox/content/issue-properties.tsx
  • apps/web/core/components/inbox/content/issue-root.tsx
  • apps/web/core/components/inbox/inbox-filter/applied-filters/date.tsx
  • apps/web/core/components/inbox/inbox-filter/applied-filters/label.tsx
  • apps/web/core/components/inbox/inbox-filter/applied-filters/root.tsx
  • apps/web/core/components/inbox/inbox-filter/applied-filters/state.tsx
  • apps/web/core/components/inbox/inbox-filter/applied-filters/status.tsx
  • apps/web/core/components/inbox/inbox-filter/constants.ts
  • apps/web/core/components/inbox/inbox-filter/filters/date.tsx
  • apps/web/core/components/inbox/inbox-filter/filters/filter-selection.tsx
  • apps/web/core/components/inbox/inbox-filter/filters/labels.tsx
  • apps/web/core/components/inbox/inbox-filter/filters/members.tsx
  • apps/web/core/components/inbox/inbox-filter/filters/state.tsx
  • apps/web/core/components/inbox/inbox-filter/filters/status.tsx
  • apps/web/core/components/inbox/inbox-filter/root.tsx
  • apps/web/core/components/inbox/modals/create-modal/create-root.tsx
  • apps/web/core/components/inbox/modals/create-modal/issue-description.tsx
  • apps/web/core/components/inbox/modals/create-modal/issue-properties.tsx
  • apps/web/core/components/inbox/modals/select-duplicate.tsx
  • apps/web/core/components/inbox/sidebar/inbox-list-item.tsx
  • apps/web/core/components/inbox/sidebar/root.tsx
  • apps/web/core/components/onboarding/invitations.tsx
  • apps/web/core/components/onboarding/invite-members.tsx
  • apps/web/core/components/onboarding/steps/profile/root.tsx
  • apps/web/core/components/onboarding/steps/profile/set-password.tsx
  • apps/web/core/components/onboarding/steps/team/root.tsx
  • apps/web/core/components/onboarding/steps/usecase/root.tsx
  • apps/web/core/components/onboarding/steps/workspace/create.tsx
  • apps/web/core/components/onboarding/steps/workspace/join-invites.tsx
  • apps/web/core/components/stickies/modal/stickies.tsx
  • apps/web/core/layouts/auth-layout/workspace-wrapper.tsx
  • packages/editor/package.json
  • packages/editor/src/core/components/links/link-edit-view.tsx
  • packages/i18n/src/index.ts
  • packages/i18n/src/locales/cs/accessibility.json
  • packages/i18n/src/locales/cs/auth.json
  • packages/i18n/src/locales/cs/common.json
  • packages/i18n/src/locales/cs/cycle.json
  • packages/i18n/src/locales/cs/editor.json
  • packages/i18n/src/locales/cs/empty-state.json
  • packages/i18n/src/locales/cs/home.json
  • packages/i18n/src/locales/cs/inbox.json
  • packages/i18n/src/locales/cs/integration.json
  • packages/i18n/src/locales/cs/navigation.json
  • packages/i18n/src/locales/cs/page.json
  • packages/i18n/src/locales/cs/project.json
  • packages/i18n/src/locales/cs/template.json
  • packages/i18n/src/locales/cs/work-item-type.json
  • packages/i18n/src/locales/cs/work-item.json
  • packages/i18n/src/locales/cs/workspace.json
  • packages/i18n/src/locales/de/accessibility.json
  • packages/i18n/src/locales/de/auth.json
  • packages/i18n/src/locales/de/common.json
  • packages/i18n/src/locales/de/cycle.json
  • packages/i18n/src/locales/de/editor.json
  • packages/i18n/src/locales/de/empty-state.json
  • packages/i18n/src/locales/de/home.json
  • packages/i18n/src/locales/de/inbox.json
  • packages/i18n/src/locales/de/integration.json
  • packages/i18n/src/locales/de/page.json
  • packages/i18n/src/locales/de/project-settings.json
  • packages/i18n/src/locales/de/project.json
  • packages/i18n/src/locales/de/template.json
  • packages/i18n/src/locales/de/work-item-type.json
  • packages/i18n/src/locales/de/work-item.json
  • packages/i18n/src/locales/de/workspace.json
  • packages/i18n/src/locales/en/accessibility.json
  • packages/i18n/src/locales/en/auth.json
  • packages/i18n/src/locales/en/common.json
  • packages/i18n/src/locales/en/editor.json
  • packages/i18n/src/locales/en/empty-state.json
  • packages/i18n/src/locales/en/inbox.json
  • packages/i18n/src/locales/en/page.json
  • packages/i18n/src/locales/en/project.json
  • packages/i18n/src/locales/en/work-item.json
  • packages/i18n/src/locales/en/workspace.json
  • packages/i18n/src/locales/es/accessibility.json
  • packages/i18n/src/locales/es/auth.json
  • packages/i18n/src/locales/es/automation.json
  • packages/i18n/src/locales/es/common.json
  • packages/i18n/src/locales/es/cycle.json
  • packages/i18n/src/locales/es/editor.json
  • packages/i18n/src/locales/es/empty-state.json
  • packages/i18n/src/locales/es/inbox.json
  • packages/i18n/src/locales/es/integration.json
  • packages/i18n/src/locales/es/navigation.json
  • packages/i18n/src/locales/es/page.json
  • packages/i18n/src/locales/es/project-settings.json
  • packages/i18n/src/locales/es/project.json
  • packages/i18n/src/locales/es/work-item.json
  • packages/i18n/src/locales/es/workspace-settings.json
  • packages/i18n/src/locales/es/workspace.json
  • packages/i18n/src/locales/fr/accessibility.json
  • packages/i18n/src/locales/fr/auth.json
  • packages/i18n/src/locales/fr/common.json
  • packages/i18n/src/locales/fr/editor.json
  • packages/i18n/src/locales/fr/empty-state.json
  • packages/i18n/src/locales/fr/inbox.json
  • packages/i18n/src/locales/fr/page.json
  • packages/i18n/src/locales/fr/power-k.json
  • packages/i18n/src/locales/fr/project-settings.json
  • packages/i18n/src/locales/fr/project.json
  • packages/i18n/src/locales/fr/tour.json
  • packages/i18n/src/locales/fr/wiki.json
  • packages/i18n/src/locales/fr/work-item-type.json
  • packages/i18n/src/locales/fr/work-item.json
  • packages/i18n/src/locales/fr/workspace-settings.json
  • packages/i18n/src/locales/fr/workspace.json
  • packages/i18n/src/locales/id/accessibility.json
  • packages/i18n/src/locales/id/auth.json
  • packages/i18n/src/locales/id/automation.json
  • packages/i18n/src/locales/id/common.json
  • packages/i18n/src/locales/id/cycle.json
  • packages/i18n/src/locales/id/editor.json
  • packages/i18n/src/locales/id/empty-state.json
  • packages/i18n/src/locales/id/inbox.json
  • packages/i18n/src/locales/id/integration.json
  • packages/i18n/src/locales/id/page.json
  • packages/i18n/src/locales/id/project.json
  • packages/i18n/src/locales/id/work-item.json
  • packages/i18n/src/locales/id/workspace.json
  • packages/i18n/src/locales/it/accessibility.json
  • packages/i18n/src/locales/it/auth.json
  • packages/i18n/src/locales/it/common.json
  • packages/i18n/src/locales/it/cycle.json
  • packages/i18n/src/locales/it/editor.json
  • packages/i18n/src/locales/it/empty-state.json
  • packages/i18n/src/locales/it/inbox.json
  • packages/i18n/src/locales/it/integration.json
  • packages/i18n/src/locales/it/navigation.json
  • packages/i18n/src/locales/it/notification.json
  • packages/i18n/src/locales/it/page.json
  • packages/i18n/src/locales/it/project.json
  • packages/i18n/src/locales/it/work-item-type.json
  • packages/i18n/src/locales/it/work-item.json
  • packages/i18n/src/locales/it/workspace-settings.json
  • packages/i18n/src/locales/it/workspace.json
  • packages/i18n/src/locales/ja/accessibility.json
  • packages/i18n/src/locales/ja/auth.json
  • packages/i18n/src/locales/ja/automation.json
  • packages/i18n/src/locales/ja/common.json
  • packages/i18n/src/locales/ja/editor.json
  • packages/i18n/src/locales/ja/empty-state.json
  • packages/i18n/src/locales/ja/inbox.json
  • packages/i18n/src/locales/ja/page.json
  • packages/i18n/src/locales/ja/project.json
  • packages/i18n/src/locales/ja/work-item.json
  • packages/i18n/src/locales/ja/workspace.json
  • packages/i18n/src/locales/ko/accessibility.json
  • packages/i18n/src/locales/ko/auth.json
  • packages/i18n/src/locales/ko/common.json
  • packages/i18n/src/locales/ko/cycle.json
  • packages/i18n/src/locales/ko/editor.json
  • packages/i18n/src/locales/ko/empty-state.json
  • packages/i18n/src/locales/ko/home.json
  • packages/i18n/src/locales/ko/inbox.json
  • packages/i18n/src/locales/ko/integration.json
  • packages/i18n/src/locales/ko/navigation.json
  • packages/i18n/src/locales/ko/page.json
  • packages/i18n/src/locales/ko/project.json
  • packages/i18n/src/locales/ko/work-item.json
  • packages/i18n/src/locales/ko/workspace.json
  • packages/i18n/src/locales/pl/accessibility.json
  • packages/i18n/src/locales/pl/auth.json
  • packages/i18n/src/locales/pl/common.json
  • packages/i18n/src/locales/pl/editor.json
  • packages/i18n/src/locales/pl/empty-state.json
  • packages/i18n/src/locales/pl/home.json
  • packages/i18n/src/locales/pl/inbox.json
  • packages/i18n/src/locales/pl/integration.json
  • packages/i18n/src/locales/pl/navigation.json
  • packages/i18n/src/locales/pl/notification.json
  • packages/i18n/src/locales/pl/page.json
  • packages/i18n/src/locales/pl/project.json
  • packages/i18n/src/locales/pl/work-item.json
  • packages/i18n/src/locales/pl/workspace.json
  • packages/i18n/src/locales/pt-BR/accessibility.json
  • packages/i18n/src/locales/pt-BR/auth.json
  • packages/i18n/src/locales/pt-BR/automation.json
  • packages/i18n/src/locales/pt-BR/common.json
  • packages/i18n/src/locales/pt-BR/editor.json
  • packages/i18n/src/locales/pt-BR/empty-state.json
  • packages/i18n/src/locales/pt-BR/inbox.json
  • packages/i18n/src/locales/pt-BR/integration.json
  • packages/i18n/src/locales/pt-BR/navigation.json
  • packages/i18n/src/locales/pt-BR/page.json
  • packages/i18n/src/locales/pt-BR/project.json
  • packages/i18n/src/locales/pt-BR/template.json
  • packages/i18n/src/locales/pt-BR/wiki.json
  • packages/i18n/src/locales/pt-BR/work-item-type.json
  • packages/i18n/src/locales/pt-BR/work-item.json
  • packages/i18n/src/locales/pt-BR/workspace.json
  • packages/i18n/src/locales/ro/accessibility.json
  • packages/i18n/src/locales/ro/auth.json
  • packages/i18n/src/locales/ro/common.json
  • packages/i18n/src/locales/ro/editor.json
  • packages/i18n/src/locales/ro/empty-state.json
  • packages/i18n/src/locales/ro/home.json
  • packages/i18n/src/locales/ro/inbox.json
  • packages/i18n/src/locales/ro/integration.json
  • packages/i18n/src/locales/ro/navigation.json
  • packages/i18n/src/locales/ro/page.json
  • packages/i18n/src/locales/ro/power-k.json
  • packages/i18n/src/locales/ro/project.json
  • packages/i18n/src/locales/ro/template.json
  • packages/i18n/src/locales/ro/tour.json
  • packages/i18n/src/locales/ro/update.json
  • packages/i18n/src/locales/ro/work-item.json
  • packages/i18n/src/locales/ro/workspace.json
  • packages/i18n/src/locales/ru/accessibility.json
  • packages/i18n/src/locales/ru/auth.json
  • packages/i18n/src/locales/ru/common.json
  • packages/i18n/src/locales/ru/editor.json
  • packages/i18n/src/locales/ru/empty-state.json
  • packages/i18n/src/locales/ru/inbox.json
  • packages/i18n/src/locales/ru/integration.json
  • packages/i18n/src/locales/ru/notification.json
  • packages/i18n/src/locales/ru/page.json
  • packages/i18n/src/locales/ru/project.json
  • packages/i18n/src/locales/ru/template.json
  • packages/i18n/src/locales/ru/tour.json
  • packages/i18n/src/locales/ru/wiki.json
  • packages/i18n/src/locales/ru/work-item-type.json
  • packages/i18n/src/locales/ru/work-item.json
  • packages/i18n/src/locales/ru/workflow.json
  • packages/i18n/src/locales/ru/workspace.json
  • packages/i18n/src/locales/sk/accessibility.json
  • packages/i18n/src/locales/sk/auth.json
  • packages/i18n/src/locales/sk/common.json
  • packages/i18n/src/locales/sk/editor.json
  • packages/i18n/src/locales/sk/empty-state.json
  • packages/i18n/src/locales/sk/inbox.json
  • packages/i18n/src/locales/sk/navigation.json
  • packages/i18n/src/locales/sk/page.json
  • packages/i18n/src/locales/sk/project.json
  • packages/i18n/src/locales/sk/settings.json
  • packages/i18n/src/locales/sk/template.json
  • packages/i18n/src/locales/sk/work-item-type.json
  • packages/i18n/src/locales/sk/work-item.json
  • packages/i18n/src/locales/sk/workspace-settings.json
  • packages/i18n/src/locales/sk/workspace.json
  • packages/i18n/src/locales/tr-TR/accessibility.json
  • packages/i18n/src/locales/tr-TR/auth.json
  • packages/i18n/src/locales/tr-TR/common.json
  • packages/i18n/src/locales/tr-TR/cycle.json
  • packages/i18n/src/locales/tr-TR/editor.json
  • packages/i18n/src/locales/tr-TR/empty-state.json
  • packages/i18n/src/locales/tr-TR/home.json
  • packages/i18n/src/locales/tr-TR/inbox.json
  • packages/i18n/src/locales/tr-TR/integration.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Localize remaining hardcoded UI strings across web, space, editor, and shared surfaces using the existing react-i18next namespace JSON files introduced by makeplane#8898.

Add the new keys to the relevant auth, common, editor, empty-state, inbox, page, project, work-item, and workspace locale files across all supported locales.

Co-authored-by: Codex <noreply@openai.com>
@xuhongbo xuhongbo force-pushed the i18n/localize-hardcoded-ui-v2 branch from 0ca8de5 to 03c1a8d Compare May 15, 2026 03:35
@xuhongbo
Copy link
Copy Markdown
Author

@coderabbitai you can now review the entire change. (Skip .json files)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the list of changed files (non-JSON) in PR `#9082`
gh pr view 9082 --repo makeplane/plane --json files --jq '.files[].path' | grep -v '\.json$'

Length of output: 4774


🏁 Script executed:

#!/bin/bash
# Get the full diff for non-JSON files in PR `#9082`
gh pr diff 9082 --repo makeplane/plane | grep -v '^diff --git.*\.json' | python3 - <<'PYEOF'
import sys

content = sys.stdin.read()

# Split by diff headers
import re
chunks = re.split(r'(?=^diff --git )', content, flags=re.MULTILINE)

# Filter out JSON file diffs
non_json_chunks = [c for c in chunks if not re.match(r'diff --git a/.*\.json', c)]

print(''.join(non_json_chunks))
PYEOF

Length of output: 1


🏁 Script executed:

#!/bin/bash
# Get the diff differently
gh api repos/makeplane/plane/pulls/9082/files --paginate --jq '.[] | select(.filename | endswith(".json") | not) | "=== FILE: \(.filename) ===\n\(.patch // "no patch")"' 2>/dev/null | head -5000

Length of output: 203988


@xuhongbo — here is the full review of the non-JSON changes in this PR. Overall the approach is solid and the i18n coverage is thorough; a few items below are worth addressing.


🔍 Code Review

🐛 Issues / Potential Regressions

1. autoFocus removed from the link edit dialog's URL input (packages/editor/src/core/components/links/link-edit-view.tsx)

The autoFocus prop was dropped from InputView entirely. Unlike page-load auto-focus (which has SSR/testing concerns), this dialog is opened by user action, so auto-focusing the URL field is the correct UX. Without it, users must manually click/tab before typing.

-function InputView({ label, value, placeholder, onChange, autoFocus }: InputViewProps) {
+function InputView({ label, value, placeholder, onChange }: InputViewProps) {
   ...
-      autoFocus={autoFocus}

Suggestion: Keep autoFocus on the URL field, or use a useEffect(() => { ref.current?.focus(); }, []) pattern like the email form does in this same PR, so the first input receives focus when the dialog opens.


2. autoFocus removed from filter search inputs

Both apps/web/core/components/inbox/inbox-filter/filters/filter-selection.tsx and apps/space/components/issues/filters/selection.tsx had autoFocus (or autoFocus={!isMobile}) removed from their search inputs. For a dropdown that opens on user action, this means desktop users need an extra click before they can type. The email form in this PR uses useEffect instead — consider applying the same pattern here.


3. Duplicate PAST_DURATION_FILTER_I18N_KEYS map

The exact same constant is defined in two files:

  • apps/web/core/components/inbox/inbox-filter/filters/date.tsx
  • apps/web/core/components/inbox/inbox-filter/applied-filters/date.tsx

Consider extracting it to a shared location (e.g. a constants.ts in the inbox filter directory) to keep it DRY.


4. Hardcoded English defaults in Spinner and Banner components

// packages/propel/src/spinners/circular-spinner.tsx
export function Spinner({ label = "Loading..." }: ISpinner)

// packages/propel/src/banner/banner.tsx
dismissLabel = "Dismiss banner",

These propel-level components are locale-agnostic — the defaults will always render English regardless of the active locale. Since callers already hold a t() instance, it is better to make these props required (or document that callers must always pass the translated string) rather than silently falling back to English.


5. Fragile sentence-splitting for Terms & Conditions (apps/space/components/account/terms-and-conditions.tsx)

{isSignUp ? t("space_public.by_creating_account") : t("space_public.by_signing_in")},{" "}
{t("space_public.you_agree_to_our")}

Splitting a single grammatical sentence across three separate keys is fragile for languages that have different word order or punctuation rules. A better pattern is to use a single key with an interpolated variable and render the link separately, or use Trans from react-i18next which handles inline elements within translated strings.


✅ Noteworthy Improvements Bundled In

These are positive changes worth acknowledging:

  • divbutton conversions throughout inbox filters applied-filters, sidebar tab navigation, onboarding invitations — good accessibility fixes.
  • Logic fix in join-invites.tsx: invitationsRespond.length <= 0 && !invitation?.role changed to || !selectedInvitation?.role. The original && would allow submission with no invitations selected as long as a role existed; || is correct.
  • tabIndex={1}tabIndex={0} in peek-overview/header.tsxtabIndex=1 breaks natural tab order; 0 is correct.
  • isButtonDisabled logic simplifications in password.tsx, usecase/root.tsx, profile/root.tsx — removes the confusing double-negation pattern.
  • Complete useMemo/useCallback dependency arrays (e.g. issue-root.tsx adding archiveIssue, projectId, removeIssue, t, workspaceSlug).
  • role="dialog" + aria-label added to link edit view container.
  • return added before router.push in async .then() callbacks — avoids unhandled floating promises.
  • onClick removed from <img> in profile/root.tsx — images with click handlers are an accessibility anti-pattern; the labeled button below it is the correct interaction target.
  • Shadow-variable renames (labellabelInfo, membermemberInfo, emailemailId, pageIdtargetPageId) — improves readability.

💬 Minor Notes

  • t("space_auth.confirm_password") is used for both the <label> and the placeholder in password.tsx. A dedicated placeholder key (e.g. space_auth.confirm_password_placeholder) would be more descriptive.
  • The error_description_prefix / error_description_middle key names in apps/space/app/error.tsx are not very descriptive — something like error_description_before_email / error_description_before_forum would communicate intent more clearly.
  • return undefined in .then() handlers (e.g. peek-overview/header.tsx, issue-properties.tsx) satisfies TypeScript's void return type but looks unusual — a comment or type cast would make the intent clearer to future readers.

xuhongbo and others added 2 commits May 15, 2026 12:09
Restore initial focus for the localized link editor and filter panels without using the autoFocus attribute, keeping the keyboard-first interaction while satisfying the a11y lint rules.

Co-authored-by: Codex <noreply@openai.com>
Tighten a small batch of locale strings against the English source for command-style actions, work item terminology, and workspace invitation messaging.

Co-authored-by: Codex <noreply@openai.com>
@xuhongbo
Copy link
Copy Markdown
Author

@coderabbitai review the latest changes. Skip .json files.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get all changed non-JSON files in PR `#9082` with their patches
gh api repos/makeplane/plane/pulls/9082/files --paginate \
  --jq '.[] | select(.filename | endswith(".json") | not) | "=== FILE: \(.filename) ===\nSTATUS: \(.status)\n\(.patch // "no patch")\n"' 2>/dev/null

Length of output: 207639


@xuhongbo — here is the review of the latest non-JSON changes.


🔍 Code Review

✅ Previous Issues Addressed

All the major items flagged in the last review have been resolved:

  • autoFocususeEffect + ref in link-edit-view.tsx, filter-selection.tsx (both apps/web and apps/space), and email.tsx.
  • tabIndex={1}tabIndex={0} in peek-overview/header.tsx.
  • isButtonDisabled double-negation cleaned up in password.tsx, usecase/root.tsx, profile/root.tsx.
  • useMemo dependency array completed in issue-root.tsx.
  • role="dialog" + aria-label added to the link edit view.
  • return before router.push in async callbacks.
  • onClick removed from <img> in profile/root.tsx.
  • Shadow-variable renames (label → labelInfo, member → memberInfo, email → emailId, value → roleDetail, pageId → targetPageId).
  • divbutton conversions throughout inbox filters, onboarding invitations, sidebar tabs, workspace-wrapper sign-out button.
  • join-invites.tsx logic fix: && correctly changed to ||.

🐛 Remaining / New Issues

1. Duplicate PAST_DURATION_FILTER_I18N_KEYS map (still present)

The identical constant is still defined in both:

  • apps/web/core/components/inbox/inbox-filter/filters/date.tsx
  • apps/web/core/components/inbox/inbox-filter/applied-filters/date.tsx

This was flagged in the previous review and is still open. Consider extracting it to a shared constants.ts in the inbox filter directory.


2. Spinner and Banner English defaults (still present)

// packages/propel/src/spinners/circular-spinner.tsx
label = "Loading..."

// packages/propel/src/banner/banner.tsx
dismissLabel = "Dismiss banner",

Both props are now properly wired (good progress), but the hardcoded English defaults remain. Callers that don't pass a translated string will still silently display English text. Since propel is locale-agnostic, these defaults should ideally be documented as "callers are required to pass a translated string," or the props should be made required.


3. Fragile Terms & Conditions string splitting (still present)

// apps/space/components/account/terms-and-conditions.tsx
{isSignUp ? t("space_public.by_creating_account") : t("space_public.by_signing_in")},{" "}
{t("space_public.you_agree_to_our")}

This is still the same split-key pattern. Languages with different word order or punctuation will produce unnatural output. The <Trans> component from react-i18next is the idiomatic solution for sentences with embedded links.


4. localizedLabels object reconstructed every render (missing useMemo)

In three files, a new localizedLabels object is created inline on every render:

// apps/space/components/issues/issue-layouts/kanban/default.tsx
const groupList = getGroupByColumns(..., {
  allWorkItems: `${t("common.all")} ${t("work_items")}`,
  none: t("common.none"),
});

// apps/space/components/issues/issue-layouts/kanban/swimlanes.tsx
const localizedLabels = {
  allWorkItems: `${t("common.all")} ${t("work_items")}`,
  none: t("common.none"),
};

// apps/space/components/issues/issue-layouts/list/default.tsx
const groupList = getGroupByColumns(..., {
  allWorkItems: `${t("common.all")} ${t("work_items")}`,
  none: t("common.none"),
});

Since t is stable across renders (same locale), this is functionally correct but allocates a new object reference on every render. In swimlanes.tsx the object is extracted to localizedLabels but still not memoized. Wrapping it in useMemo (or useRef initialized once) would be more efficient, especially for kanban boards that re-render frequently.

const localizedLabels = useMemo(() => ({
  allWorkItems: `${t("common.all")} ${t("work_items")}`,
  none: t("common.none"),
}), [t]);

5. archive parameters named with underscore prefix but immediately used

In apps/web/core/components/inbox/content/issue-root.tsx:

archive: async (_workspaceSlug: string, _projectId: string, issueId: string) => {
  try {
    await archiveIssue(_workspaceSlug, _projectId, issueId);
  } catch (error) { ... }
},

The convention of _ prefix signals "unused" to both TypeScript and linters, but _workspaceSlug and _projectId are actively used in the function body. This will likely trigger linter warnings. Either remove the underscores, or capture the outer workspaceSlug/projectId from the closure (which is the simpler fix since those values are already in scope).


💬 Minor Notes

  • apps/space/app/error.tsx and instance-failure-view.tsx both move handleRetry to module scope — this is a valid pattern but means the function is shared across all instances/renders, which is fine for a simple window.location.reload().
  • apps/web/core/components/inbox/content/inbox-issue-header.tsx: handleInboxIssueDelete now calls deleteInboxIssue and then unconditionally calls router.push outside the .then() — this means the redirect fires even if the delete fails. The original .then() chaining guarded the redirect. Consider wrapping in try/catch.
  • t("space_auth.confirm_password") is used for both the <label> and placeholder in password.tsx — still uses the same key for both (noted in prior review as a minor item, still present).

@xuhongbo
Copy link
Copy Markdown
Author

@codex review the latest changes. Skip .json files.

@xuhongbo
Copy link
Copy Markdown
Author

@claude review the latest changes. Skip .json files.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae52e18462

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +85 to +88
<button
type="button"
key={invitation.id}
className={`flex cursor-pointer items-center gap-2 rounded-lg border border-subtle px-3 py-2 hover:bg-surface-2`}
className={`flex w-full cursor-pointer items-center gap-2 rounded-lg border border-subtle px-3 py-2 text-left hover:bg-surface-2`}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep invitation row non-button when rendering checkbox

This row was switched to a <button> while it still contains <Checkbox />, and Checkbox renders an actual <input type="checkbox">. Nesting one interactive control inside another is invalid HTML and causes inconsistent keyboard/screen-reader behavior (the row and checkbox become competing controls), so invitation selection can become unreliable for assistive-tech users. Keep the row as a non-button container with explicit click/keyboard handlers, or render a non-interactive check indicator inside the button.

Useful? React with 👍 / 👎.

@xuhongbo
Copy link
Copy Markdown
Author

@coderabbitai I reviewed the remaining items.\n\n- : agreed the / names are misleading since they are used. I am treating that as a naming cleanup, not a behavioral issue for this PR.\n- : the current flow is and only then . With normal async semantics, the redirect does not run if the delete call rejects, so I am not treating this as a confirmed regression from the current code.\n- The duplicated date filter map, / English defaults, and the split Terms & Conditions sentence are valid follow-up cleanup ideas, but they are outside the scope of this i18n PR.\n\nI am keeping this PR focused on localized strings and the minimum code changes required to preserve upstream behavior.

@xuhongbo
Copy link
Copy Markdown
Author

@coderabbitai I reviewed the remaining items.

  • issue-root.tsx: agreed the _workspaceSlug / _projectId names are misleading since they are used. I am treating that as a naming cleanup, not a behavioral issue for this PR.
  • inbox-issue-header.tsx: the current flow is await deleteInboxIssue(...) and only then router.push(...). With normal async semantics, the redirect does not run if the delete call rejects, so I am not treating this as a confirmed regression from the current code.
  • The duplicated date filter map, Spinner / Banner English defaults, and the split Terms & Conditions sentence are valid follow-up cleanup ideas, but they are outside the scope of this i18n PR.

I am keeping this PR focused on localized strings and the minimum code changes required to preserve upstream behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Tip

For best results, initiate chat on the files or code changes.

@xuhongbo — thanks for the thorough walkthrough. All of the reasoning is sound:

  • The _-prefixed-but-used parameters are indeed a style/naming cleanup with no behavioral impact; happy to treat them that way.
  • On handleInboxIssueDelete: you're right that a rejected await will throw before the subsequent router.push line is reached, so the guard is still in place — I withdraw that concern.
  • The three deferred items (duplicate PAST_DURATION_FILTER_I18N_KEYS, Spinner/Banner English-only defaults, <Trans>-based Terms & Conditions) are reasonable follow-up candidates.

Would you like me to open follow-up GitHub issues for the three deferred items so they don't get lost? I can assign them to you and backlink this PR.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Tip

For best results, initiate chat on the files or code changes.

@xuhongbo — understood on all three points, thanks for the clear explanations:

  • issue-root.tsx — naming-only cleanup, agreed.
  • inbox-issue-header.tsxawait before router.push provides the guard; concern withdrawn.
  • Deferred items — all three (duplicate PAST_DURATION_FILTER_I18N_KEYS, Spinner/Banner English defaults, <Trans>-based Terms & Conditions) are reasonable follow-ups but out of scope here.

Would you like me to open GitHub issues for the three deferred items so they don't get lost between PRs? I can create them now and backlink this PR.

@xuhongbo
Copy link
Copy Markdown
Author

@coderabbitai Thanks. Please do not open follow-up issues automatically for now.

Reasoning:

  • None of the three deferred items are blockers for this PR.
  • They span different scopes: shared component defaults (Spinner / Banner), translation structure (<Trans> for Terms & Conditions), and a small DRY cleanup (PAST_DURATION_FILTER_I18N_KEYS). I do not want to bundle them into ad hoc issues without first deciding the right ownership and priority after this i18n PR lands.
  • I want to keep this PR narrowly focused on localized strings and the minimum code changes required to preserve upstream behavior.

I will track those separately later if they still matter after merge.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Tip

For best results, initiate chat on the files or code changes.

@xuhongbo — understood, I won't open issues for those items. The three deferred follow-ups (duplicate PAST_DURATION_FILTER_I18N_KEYS, Spinner/Banner English-only defaults, <Trans>-based Terms & Conditions) are noted and can be revisited after this PR lands whenever you decide on ownership and priority.

🐇 。 ✿


✏️ Learnings added
Learnt from: xuhongbo
URL: https://github.com/makeplane/plane/pull/9082

Timestamp: 2026-05-15T04:50:49.188Z
Learning: For the makeplane/plane repository, do not automatically open follow-up GitHub issues for deferred or out-of-scope review items. The team prefers to track those separately and decide ownership/priority after the relevant PR lands.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Co-authored-by: Codex <noreply@openai.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e88539e4e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +136 to +137
title: t("space_auth.sign_out.toast.error.title"),
message: t("space_auth.sign_out.toast.error.message"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use existing auth sign-out error translation keys

When signOut() fails, this toast resolves space_auth.sign_out.toast.error.*, but the locale files define those messages under auth.sign_out.toast.error.* (for example in packages/i18n/src/locales/en/auth.json) and do not include a space_auth.sign_out branch. In that failure path, users will see untranslated key strings instead of a readable localized error.

Useful? React with 👍 / 👎.

Co-authored-by: Codex <noreply@openai.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da3874f697

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +32 to +34
useEffect(() => {
searchInputRef.current?.focus();
}, []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep mobile guard before focusing filter search input

This effect now focuses the search field every time the filter panel mounts, but the previous implementation explicitly skipped autofocus on mobile (autoFocus={!isMobile}). On phones/tablets this will immediately open the soft keyboard when the filter UI opens, obscuring the options list and degrading filter selection flow; the focus call should stay conditional for non-mobile contexts.

Useful? React with 👍 / 👎.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 16, 2026

CLA assistant check
All committers have signed the CLA.

xuhongbo added 2 commits May 16, 2026 11:26
- Replace split-key TermsAndConditions with Trans component so
  sentence structure stays correct in all 18 supported languages;
  add composite terms_sign_up / terms_sign_in keys to every auth.json
- Export Trans from @plane/i18n for consistent import convention
- Restore isMobile guard in InboxIssueFilterSelection useEffect
  so mobile users are not unexpectedly focused on the search input
- Deduplicate PAST_DURATION_FILTER_I18N_KEYS into a shared
  inbox-filter/constants.ts; both filter and applied-filter date
  components now import from the single source of truth

https://claude.ai/code/session_01CLFmPiRwMYkD6BeSZxhE6B
Apply 信达雅 (faithfulness/expressiveness/elegance) review findings from
multi-agent professional translation audit:

**Critical fixes (high severity):**
- zh-CN/zh-TW: Fix "good"+"morning" concatenation bug ("早上早上"/"早安早上"
  → "早上好"/"早安") by emptying `good` and moving full greeting to time keys
- zh-CN/zh-TW: Fix "Intake Trends" mistranslated as "入学趋势" (enrollment
  trends) → "需求收集趋势/趨勢" (intake/requirements trends)
- zh-CN: Fix `onboarding.profile_setup_failed` "配置文件设置失败"
  → "个人资料设置失败" ("config file" vs "profile")
- zh-CN: Fix snooze "暂停" (pause) → "延后" (defer/postpone)
- ja: Fix severe phonetic transliterations back to Japanese
  ("クリエイテッド アット"→"作成日時", "セービング"→"保存中", etc.)
- ru: Fix "Пейдж" (wrong transliteration) → "Страница" for `common.page`
- ru: Fix gender agreement "Новое пароль" → "Новый пароль"
- ru: Fix spelling "высококонтрастностная" → "высококонтрастная"
- de: Fix typo "Däschbords" → "Dashboards"
- sk: Fix verbal aspect error "Prepínať" (imperfective) → "Prepnúť"
- sk: Fix "Nahranie diela" (artwork upload) → "Nahranie súboru" (file upload)
- pl: Fix grammar "Zanim" (conjunction) → "Przed" (preposition) for date filter
- tr-TR/es: Fix placeholder "alır-atar-uçar"/"obtiene-establece-vuela" → "123456"
- fr/it/ua/tr-TR: Fix &apos; HTML entities in JSON strings (72 instances total)
- zh-TW: Fix "文件" (document) → "檔案" (file) in file_upload, fix "字段"→"欄位"
- zh-TW: Fix "技術支援" (tech support) → "Powered by" for space_public.powered_by
- zh-TW: Fix "選修的" (elective course) → "選填" (optional field)

**Structural fixes:**
- All 18 languages: Translate 6 untranslated keys that remained as English
  (completed_on, your_profile, developer, work_structure, execution, administration)
- All 18 languages: Fix `common.order_by.manual` removing spurious "- Ранг/ランク"
  additions not present in English source
- All 18 languages: Fix `pi_chat` from "AI Chat" variants → "Plane AI" (brand name)
- All 18 languages: Fix `attachmentComponent.uploader.drag_and_drop` - restore
  missing "or click to upload" option (was truncated to drag-only in 14 languages)
- All 18 languages: Remove hardcoded `\n` from `issue.archive.description`

**Plural/grammar fixes:**
- ru/cs/pl/sk: Fix `inbox.sidebar.label_count` with proper ICU plural rules
- ru: Fix `inbox_issue.snoozed.description` missing "few" plural category
- ru: Fix `space_auth.resend_in` trailing English "s" → Russian "с"
- ua: Fix `space_auth.resend_in` and `go_to_workspace` form consistency
- ua: Fix `space_auth.go_to_workspace` imperative → infinitive

**Consistency fixes:**
- zh-TW workspace.json: Replace "創建"→"建立", "工作空間"→"工作區" throughout
- cs/editor.json: Fix "Centrum"→"Střed" for alignment label, fix italic case
- pl/editor.json: Unify alignment labels (left/center/right), fix insert_left
- ru/editor.json: Fix `editor.menu_items.quote` verb→noun form
- ru/work-item.json: Fix `issue.sibling.label` "related"→"sibling" semantics
- ro/auth.json: Fix missing diacritics (Continua→Continuă)
- it/inbox.json: Fix snooze action label
- ko: Fix greetings to natural Korean (안녕하세요 pattern)
- ja/auth.json: Fix resend_in "{seconds}s" → "{seconds}秒後に再送信"
- ko/auth.json: Fix resend_in "{seconds}s" → "{seconds}초 후에 재전송"

https://claude.ai/code/session_01CLFmPiRwMYkD6BeSZxhE6B
@xuhongbo xuhongbo force-pushed the i18n/localize-hardcoded-ui-v2 branch from 0ca95a0 to 3af7f0f Compare May 16, 2026 03:26
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3af7f0fd3c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 235 to 241
<button
type="button"
className="inline-flex cursor-pointer items-center gap-1.5"
onClick={() => setCreateMore((prevData) => !prevData)}
role="button"
tabIndex={getIndex("create_more")}
>
<ToggleSwitch value={createMore} onChange={() => {}} size="sm" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid nesting ToggleSwitch inside a button

Wrapping ToggleSwitch with a native <button> creates a button-inside-button structure, because @plane/ui implements ToggleSwitch with Headless UI Switch (which renders a button). In this modal footer, that invalid interactive nesting can cause duplicate/competing click and keyboard behavior (especially Enter/Space activation and screen-reader control semantics) when users toggle create more. Keep the wrapper non-interactive (or move click handling onto the switch/label) instead of using a parent button.

Useful? React with 👍 / 👎.

@xuhongbo xuhongbo force-pushed the i18n/localize-hardcoded-ui-v2 branch from 55562d8 to 5f9662f Compare May 16, 2026 04:56
Co-authored-by: Codex <noreply@openai.com>
Apply a targeted pass over Gemini-reviewed locale findings, focusing on clear mistranslations, grammatical case issues, editor terminology, count labels, date filters, and product terminology consistency across the PR locale updates.

Co-authored-by: Codex <noreply@openai.com>
@xuhongbo
Copy link
Copy Markdown
Author

Follow-up translation QA pass applied in 53a05a215f.

What changed:

  • Absorbed high-confidence Gemini review findings across the updated locale JSON files.
  • Fixed clear mistranslations and grammar/case issues in auth legal terms, onboarding copy, editor labels, inbox filters, and count labels.
  • Tightened product terminology for concepts such as upload/import, duplicate, asset/resource, work item, workspace, and rich-text editor controls.
  • Preserved placeholder/tag structure such as {count}, {seconds}, <tos>, and <privacy>.

Validation:

  • pnpm --filter=@plane/i18n run check:sync
  • git diff --check

Apply another targeted pass over Gemini-reviewed translation findings that remained clearly actionable, including UI command wording, workspace terminology, instance labels, validation messages, and product term consistency.

Co-authored-by: Codex <noreply@openai.com>
@xuhongbo
Copy link
Copy Markdown
Author

xuhongbo commented May 17, 2026

Added a second follow-up translation QA commit: 21616c8de9.

What changed in this pass:

  • Absorbed additional medium-confidence findings that were still clearly actionable after the first pass.
  • Improved remaining UI command wording, validation messages, workspace terminology, instance labels, duplicate/work-item wording, and product terminology consistency.
  • Kept deferring broad glossary-level choices where a single-key change could create new inconsistency, especially larger zh-TW work-item terminology and Russian workspace terminology.

Validation:

  • pnpm --filter=@plane/i18n run check:sync
  • git diff --check

Apply follow-up translation QA findings across locales, focusing on user-facing clarity, Plane product terminology, locale-specific wording, and glossary consistency.

Co-authored-by: Codex <noreply@openai.com>
@xuhongbo
Copy link
Copy Markdown
Author

xuhongbo commented May 17, 2026

Added another translation QA follow-up commit: 9e051bef98.

What changed:

  • Ran an additional Gemini 3.1 Pro review pass after the first absorption round, including a slower per-locale review for non-zh-TW / ua / tr-TR locales.
  • Tightened Plane product terminology and user-facing consistency across locales, especially around workspace, project, work item, module, instance, profile, duplicate relationships, editor actions, and onboarding/invitation flows.
  • Normalized zh-TW terminology for Taiwan usage, including 工作區, 專案, 工作項目, 執行個體, 模組, 預設, 搜尋, and related product-copy wording.
  • Normalized Ukrainian project / work item terminology and fixed grammar introduced by glossary consolidation.
  • Cleaned up Turkish strings with English/phonetic leftovers such as workspace/application/permission/client-secret wording.
  • Applied the final per-locale Gemini findings for Japanese invitation acceptance, Polish editor alignment, and Simplified Chinese profile wording in app-consent copy.
  • Deliberately kept a few model suggestions out where the repo-wide context was stronger, such as retaining Simplified Chinese formal 您/您的 tone and using Russian рабочее пространство for workspace.

Validation:

  • pnpm --filter=@plane/i18n run check:sync
  • git diff --check

@xuhongbo
Copy link
Copy Markdown
Author

xuhongbo commented May 19, 2026

Hi @sriramveeraghanta, could you please review this PR when you have a chance?

This is a community localization contribution against the preview branch, and it is ready for maintainer review. Thank you.

@sriramveeraghanta
Copy link
Copy Markdown
Member

@xuhongbo Thank you for your contribution. Team will review the PR and merge it.

Apply another round of translation refinements across supported non-English locales after multi-model review against the English source strings.

The review pass used qwen3.6-flash for full locale/namespace candidate generation, deepseek-v4-flash for candidate validation, gemini-3.1-pro-preview via Gemini CLI for per-locale review, and gpt-5.3-codex-spark plus Claude Code for final cross-checking. Pure style-only suggestions were filtered out and greeting strings were checked against their actual runtime composition before keeping changes.

Validation: pnpm --filter=@plane/i18n run check:sync; full i18next-icu render scan across 18 locales and 28 namespaces; structural token scan with expected plural-suffix adaptations; git diff --check.

Co-authored-by: Codex <noreply@openai.com>
@xuhongbo
Copy link
Copy Markdown
Author

@sriramveeraghanta I pushed an additional localization quality pass in d4d703f189.

Translation QA Report

Scope

This follow-up pass reviewed the non-English locale files against the English source strings again, focusing on user-facing correctness rather than broad stylistic rewrites. The goal was to catch translations that could affect product comprehension in Plane's project-management UI: work items, cycles, modules, inbox/intake flows, workspace settings, authentication, integrations, templates, and analytics surfaces.

Review Process

I used a multi-model review pipeline so that we did not rely on a single model's translation preference:

  • qwen3.6-flash: full candidate generation across 18 locales and 28 namespaces.
  • deepseek-v4-flash: second-pass validation to narrow noisy candidates.
  • gemini-3.1-pro-preview via Gemini CLI: per-locale review against the English source strings.
  • gpt-5.3-codex-spark: final cross-check on the narrowed candidate set.
  • Claude Code: independent final cross-check on the same narrowed candidate set.

Selection Criteria

I only kept suggestions that met at least one of these criteria:

  • Corrected a clear semantic mismatch with the English source.
  • Improved Plane-specific product terminology consistency, especially around work items, cycles, modules, intake, inbox, estimates, assignees, and workspace concepts.
  • Removed UI ambiguity that could cause users to misunderstand an action, status, field label, or settings description.
  • Fixed grammar or wording issues that were visibly broken or unnatural in the target language.
  • Preserved all placeholders, ICU syntax, and structural tokens such as {count}, {seconds}, <tos>, and <privacy>.

I rejected pure style preferences and also rejected model suggestions that looked reasonable in isolation but did not match the actual runtime context. For example, the greeting strings are composed in code as Good + morning/afternoon/evening, so those were checked against the UI composition before keeping or reverting any suggestion.

Validation

After applying the selected changes, I ran:

  • pnpm --filter=@plane/i18n run check:sync
  • Full i18next-icu render scan across 18 locales x 28 namespaces: 73,584 strings rendered, 0 errors.
  • Structural token scan for placeholders, tags, and ICU variables: 0 unexpected issues.
  • git diff --check

No review scripts or intermediate model outputs are included in the commit.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4d703f189

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

)}
<span className="break-words text-secondary">{row.original.display_name ?? t(`Unassigned`)}</span>
<span className="break-words text-secondary">
{row.original.display_name ?? t("common.unassigned")}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the defined unassigned translation key

t("common.unassigned") does not resolve to any locale entry, so rows without display_name will render the raw key string instead of a label. In packages/i18n/src/locales/en/common.json, the existing key is "unassigned" (not nested under common), so this new lookup regresses the assignee fallback text in the insight table.

Useful? React with 👍 / 👎.

Use the existing top-level unassigned key for the work items insight table fallback label.

Validation: pnpm --filter=@plane/i18n run check:sync; git diff --check.

Co-authored-by: Codex <noreply@openai.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ccb7e50f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

isChecked={isCustomDateSelected()}
onClick={handleCustomDate}
title="Custom"
title={t("custom")}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a date-specific key for custom date option

This replaces the literal "Custom" with t("custom"), but in en/common.json that key is "Custom theme", so the custom-date filter option now shows unrelated copy in the inbox filter UI. This is a user-visible regression whenever the date filter menu is opened; use an inbox/date-specific translation key instead of the global custom key.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature]: Expand localization coverage for hardcoded UI surfaces

3 participants